﻿#include <QApplication>
#include <QPainter>
#include <QTextBlock>
#include <QVBoxLayout>
#include <QLabel>
#include <QTextCodec>
#include <QLineEdit>
#include <QMessageBox>
#include <QMenu>
#include <QClipboard>
#include <Constants.h>
#include <Configuration.h>
#include <Resources.h>
#include <Utils.h>
#include <MessageServices.h>
#include "TextEditorWidget.h"
#include "XMLSyntax/XMLSyntaxHighlighter.h"

namespace Domy {

/**
 * @brief TextEditorWidget
 */
TextEditorWidget::TextEditorWidget(QWidget *parent)
    : AbstractEditorWidget(parent),
      m_editor(new TextEditor(this)),
      m_bar(new EditorBottomBar(this))
{
    QVBoxLayout *l = new QVBoxLayout(this);
    this->setLayout(l);

    l->addWidget(m_editor, 1);
    l->addWidget(m_bar, 0);

    l->setMargin(0);
    l->setSpacing(0);

    connect(m_bar, SIGNAL(signalCodecNameChanged(const QString &)),
            this, SLOT(slotOnCodecNameChanged(const QString &)));
    connect(m_bar, SIGNAL(signalFind(const QString &, bool, bool)),
            this, SLOT(slotOnFind(const QString &, bool, bool)));
    connect(m_bar, SIGNAL(signalFindNext(const QString &, bool, bool)),
            this, SLOT(slotOnFindNext(const QString &, bool, bool)));
    connect(m_bar, SIGNAL(signalFindPrevious(const QString &, bool, bool)),
            this, SLOT(slotOnFindPrevious(const QString &, bool, bool)));
    connect(m_bar, SIGNAL(signalClearFindResult()),
            this, SLOT(slotOnClearFindResult()));
    connect(m_bar, SIGNAL(signalReplace(const QString &, const QString &, bool, bool)),
            this, SLOT(slotOnReplace(const QString &, const QString &, bool, bool)));
    connect(m_editor, SIGNAL(modificationChanged(bool)),
            this, SLOT(slotOnContentModificationChanged(bool)));
    connect(m_editor, SIGNAL(cursorPositionChanged()),
            this, SLOT(slotOnCursorPositionChanged()));
}

TextEditorWidget::~TextEditorWidget()
{

}

TextEditor *TextEditorWidget::getEditor()
{
    return m_editor;
}

void TextEditorWidget::triggerAction(ActionKey key)
{
    auto get_find_text = [&]()->QString {

        QString find_text = m_editor->getSelectedText();
        if (find_text.isEmpty()) {
            find_text = m_bar->getFindText();
        }
        else {
            m_bar->setFindText(find_text);
        }
        return find_text;
    };

    switch (key) {
    case ActKey_Undo:
        m_editor->undo();
        break;
    case ActKey_Redo:
        m_editor->redo();
        break;
    case ActKey_Copy:
        m_editor->copy();
        break;
    case ActKey_Cut:
        m_editor->cut();
        break;
    case ActKey_Paste:
        m_editor->paste();
        break;
    case ActKey_SelectAll:
        m_editor->selectAll();
        break;
    case ActKey_Find:
    {
        QString find_text = get_find_text();
        slotOnFind(find_text, m_bar->isCaseSensitive(), m_bar->isWholeWord());
    }
        break;
    case ActKey_FindNext:
    {
        QString find_text = get_find_text();
        slotOnFindNext(find_text, m_bar->isCaseSensitive(), m_bar->isWholeWord());
    }
        break;
    case ActKey_FindPrev:
    {
        QString find_text = get_find_text();
        slotOnFindPrevious(find_text, m_bar->isCaseSensitive(), m_bar->isWholeWord());
    }
        break;
    case ActKey_ShortcutComment:
        m_editor->encaseSelectedText(TextEditor::Encase_Comment);
        break;
    case ActKey_ShortcutApostrophe:
        m_editor->encaseSelectedText(TextEditor::Encase_Apostrophe);
        break;
    case ActKey_ShortcutQuotation:
        m_editor->encaseSelectedText(TextEditor::Encase_Quotation);
        break;
    case ActKey_ShortcutAngleBracket:
        m_editor->encaseSelectedText(TextEditor::Encase_AngleBracket);
        break;
    case ActKey_ShortcutParen:
        m_editor->encaseSelectedText(TextEditor::Encase_Paren);
        break;
    case ActKey_ShortcutBracket:
        m_editor->encaseSelectedText(TextEditor::Encase_Bracket);
        break;
    case ActKey_ShortcutBrace:
        m_editor->encaseSelectedText(TextEditor::Encase_Brace);
        break;
    default:
        break;
    }
}

void TextEditorWidget::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Escape) {
        m_editor->clearFindResult();
        m_bar->setFindText("");
    }

    QWidget::keyPressEvent(event);
}

void TextEditorWidget::slotOnFind(const QString &text, bool is_case_sensitive, bool is_whole_word)
{
    m_editor->findText(text, is_case_sensitive, is_whole_word);
}

void TextEditorWidget::slotOnFindNext(const QString &text, bool is_case_sensitive, bool is_whole_word)
{
    if (!m_editor->findNext(text, is_case_sensitive, is_whole_word)) {
        domy_msg->showStatus(tr("The search result has reached the begin location !"));
        m_editor->findText(text, is_case_sensitive, is_whole_word);
    }
}

void TextEditorWidget::slotOnFindPrevious(const QString &text, bool is_case_sensitive, bool is_whole_word)
{
    if (!m_editor->findPrevious(text, is_case_sensitive, is_whole_word)) {
        domy_msg->showStatus(tr("The search result has reached the begin location !"));
    }
}

void TextEditorWidget::slotOnClearFindResult()
{
    m_editor->clearFindResult();
}

void TextEditorWidget::slotOnReplace(const QString &before, const QString &after, bool is_case_sensitive, bool is_whole_word)
{
    m_editor->findText(before, is_case_sensitive, is_whole_word);

    if (QMessageBox::information(this, tr("Replace All"),
                                                       tr("This operation cannot be undone. Do you want to continue?"),
                                                       QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
            == QMessageBox::Yes) {

        m_editor->replace(before, after, is_case_sensitive, is_whole_word);
    }
    else {
        return;
    }
}

void TextEditorWidget::slotOnCodecNameChanged(const QString &name)
{
    m_editor->setCodecName(name);

    emit signalReload();
}

void TextEditorWidget::slotOnContentModificationChanged(bool is_changed)
{
    m_bar->setCodecNameEditable(!is_changed);
}

void TextEditorWidget::slotOnCursorPositionChanged()
{
    QTextCursor cursor = m_editor->textCursor();
    m_bar->setInfo(m_editor->document()->characterCount() - 1, m_editor->document()->blockCount(),
                   cursor.blockNumber() + 1, cursor.columnNumber() + 1);
}

/**
 * @brief TextEditor
 */
TextEditor::TextEditor(QWidget *parent)
    : QPlainTextEdit(parent),
      m_num_widget(new LineNumberWidget(this)),
      m_err_line(-1),
      m_highlighter(new XMLSyntaxHighlighter(this)),
      m_tab_size(4),
      m_tab_policy(TabPolicy_Spaces)
{
    this->setWordWrapMode(QTextOption::NoWrap);
    this->setAutoFillBackground(true);
    this->setObjectName("TextEditor");
    this->setContextMenuPolicy(Qt::CustomContextMenu);

    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotOnBlockCountChanged(int)));
    connect(this, SIGNAL(updateRequest(const QRect &, int)), this, SLOT(slotOnUpdateRequest(const QRect &, int)));
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotOnCursorPositionChanged()));
    connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(slotOnCustomContextMenuRequest(const QPoint &)));

    slotOnBlockCountChanged(0);
    highlightCurrentLine();

    updateSettings();
}

TextEditor::~TextEditor()
{

}

void TextEditor::setErrorLineHighlight(int line)
{
    m_err_line = line - 1;

    QList<QTextEdit::ExtraSelection> extra_selection = this->extraSelections();

    QTextEdit::ExtraSelection selection;

    QString scheme_name = domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_SchemeName).toString();
    QLinearGradient gradient(QPointF(0, this->width()), QPointF(this->width(), this->cursorWidth()));
    gradient.setColorAt(0, QColor(Qt::red).lighter(120));
    gradient.setColorAt(1, QColor(domy_config->getColorSchemeValue(scheme_name, SettingsKey_BGColor).toString()));

    selection.format.setBackground(QBrush(gradient));
    selection.format.setProperty(QTextFormat::FullWidthSelection, true);
    selection.cursor = QTextCursor(this->document()->findBlockByLineNumber(m_err_line));
    selection.cursor.clearSelection();
    m_extra_selection.append(selection);

    setExtraSelections(extra_selection);

    highlightCurrentLine();
}

void TextEditor::clearErrorLineHighlight()
{
    m_err_line = -1;
    m_extra_selection.clear();
    highlightCurrentLine();
}

void TextEditor::updateSettings()
{
    m_codec_name = domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_Encoding).toString();

    QFont font(domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_Family).toString());
    font.setPixelSize(domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_FontSize).toInt());

    m_num_widget->setFont(font);

    m_tab_size = domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_TabSize).toInt();
    QFontMetrics metrics(font);
    this->setTabStopWidth(m_tab_size * metrics.width(' '));

    m_tab_policy = static_cast<TabPolicy>(domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_TabPolicy).toInt());

    QString scheme_name = domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_SchemeName).toString();
    m_num_widget->setBackgroundColor(QColor(domy_config->getColorSchemeValue(scheme_name, SettingsKey_LNBGColor).toString()));
    m_num_widget->setTextColor(QColor(domy_config->getColorSchemeValue(scheme_name, SettingsKey_LNTextColor).toString()));

    {
        // 更新配色方案的设置
        QStringList params = domy_config->getColorSchemeValue(scheme_name, SettingsKey_Text).toStringList();
        font.setBold(params.at(SchemeParam_Bold) == "true");
        font.setItalic(params.at(SchemeParam_Italic) == "true");
        font.setUnderline(params.at(SchemeParam_Underline) == "true");
        this->setFont(font);
        QColor text_color(params.at(SchemeParam_Color));
        QColor bg_color(domy_config->getColorSchemeValue(scheme_name, SettingsKey_BGColor).toString());
        this->setStyleSheet(QString("QPlainTextEdit#%1 { background-color: rgb(%2, %3, %4);"
                                    "color: rgb(%5, %6, %7); }")
                            .arg(this->objectName())
                            .arg(bg_color.red()).arg(bg_color.green()).arg(bg_color.blue())
                            .arg(text_color.red()).arg(text_color.green()).arg(text_color.blue()));
    }

    auto set_scheme_item = [&](const QString &scheme_name, ColorSchemeItem item, const QString &key) {
        QStringList params = domy_config->getColorSchemeValue(scheme_name, key).toStringList();
        m_highlighter->setSchemeItem(item, QColor(params.at(SchemeParam_Color)),
                                     params.at(SchemeParam_Bold) == "true",
                                     params.at(SchemeParam_Italic) == "true",
                                     params.at(SchemeParam_Underline) == "true");
    };

    // 更新配色方案的设置
    set_scheme_item(scheme_name, SchemeItem_Tag, SettingsKey_Tag);
    set_scheme_item(scheme_name, SchemeItem_Attr, SettingsKey_Attr);
    set_scheme_item(scheme_name, SchemeItem_AttrText, SettingsKey_AttrText);
    set_scheme_item(scheme_name, SchemeItem_Comment, SettingsKey_Comment);

    m_highlighter->clearSearchHighlight();

    m_highlighter->setDocument(this->document());

    m_highlighter->updateRules();

    highlightCurrentLine();
}

QString TextEditor::getCodecName() const
{
    return m_codec_name;
}

void TextEditor::setCodecName(const QString &codec_name)
{
    m_codec_name = codec_name;
}

void TextEditor::findText(const QString &text, bool is_case_sensitive, bool is_whole_word)
{
    QTextDocument::FindFlags flags = getFindFlags(false, is_case_sensitive, is_whole_word);

    QTextCursor cursor = this->document()->find(text, 0, flags);

    if (cursor.isNull()) {
        QMessageBox::information(this, tr("Not Found"), tr("Text \"%1\" not found !").arg(text),
                                 QMessageBox::Ok, QMessageBox::Ok);
        m_highlighter->clearSearchHighlight();
        m_highlighter->setDocument(this->document());
        m_highlighter->updateRules();
        return;
    }

    this->setFocus();

    this->setTextCursor(cursor);

    QString scheme_name = domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_SchemeName).toString();

    m_highlighter->setSearchHighlight(text, QColor(domy_config->getColorSchemeValue(scheme_name, SettingsKey_Search).toString()),
                                      is_case_sensitive, is_whole_word);

    m_highlighter->setDocument(this->document());

    m_highlighter->updateRules();
}

bool TextEditor::findNext(const QString &text, bool is_case_sensitive, bool is_whole_word)
{
    this->setFocus();

    QTextDocument::FindFlags flags = getFindFlags(false, is_case_sensitive, is_whole_word);

    return this->find(text, flags);
}

bool TextEditor::findPrevious(const QString &text, bool is_case_sensitive, bool is_whole_word)
{
    this->setFocus();

    QTextDocument::FindFlags flags = getFindFlags(true, is_case_sensitive, is_whole_word);

    return this->find(text, flags);
}

void TextEditor::clearFindResult()
{
    m_highlighter->clearSearchHighlight();

    m_highlighter->setDocument(this->document());

    m_highlighter->updateRules();
}

void TextEditor::replace(const QString &before, const QString &after, bool is_case_sensitive, bool is_whole_word)
{
    QString text = this->toPlainText();
    QString exp = QString("%1%2%3")
            .arg(is_whole_word ? "\\b" : "")
            .arg(before)
            .arg(is_whole_word ? "\\b" : "");

    QRegExp reg_exp = QRegExp(exp, is_case_sensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);

    text = text.replace(reg_exp, after);

    this->setPlainText(text);

    emit modificationChanged(true);
}

QString TextEditor::getSelectedText() const
{
    return textCursor().selectedText();
}

void TextEditor::encaseSelectedText(TextEditor::EncaseType encase)
{
    QTextCursor tc = this->textCursor();

    QString text = tc.selectedText();

    if (text.isEmpty() && encase != Encase_Comment) {
        return;
    }

    // 获取选中文本的起始位置
    int start_pos = tc.selectionStart();

    bool is_selected = true;
    switch (encase) {
    case Encase_Comment:
    {
        if (text.isEmpty()) {
            // 文本为空，注释选中行
            tc.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
            start_pos = tc.position();

            tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
            text = tc.selectedText();

            is_selected = false; // 多行注释不选中修改后的文本
        }

        if (text.left(4) == "<!--"
                && text.right(3) == "-->") {
            // 若当前文本首尾已有注释符号，则取消注释
            text.remove(0, 4);
            text.remove(text.length() - 3, 3);
        }
        else {
            text = QString("<!--%1-->").arg(text);
        }
    }
        break;
    case Encase_Apostrophe:
        text = QString("\'%1\'").arg(text);
        break;
    case Encase_Quotation:
        text = QString("\"%1\"").arg(text);
        break;
    case Encase_AngleBracket:
        text = QString("<%1>").arg(text);
        break;
    case Encase_Paren:
        text = QString("(%1)").arg(text);
        break;
    case Encase_Bracket:
        text = QString("[%1]").arg(text);
        break;
    case Encase_Brace:
        text = QString("{%1}").arg(text);
        break;
    default:
        break;
    }

    int end_pos = start_pos + text.length();

    tc.insertText(text);

    if (is_selected) {
        // 选中已改变的文本
        tc.setPosition(start_pos, QTextCursor::MoveAnchor);

        tc.setPosition(end_pos, QTextCursor::KeepAnchor);
    }

    this->setTextCursor(tc);
}

void TextEditor::resizeEvent(QResizeEvent *event)
{
    QPlainTextEdit::resizeEvent(event);

    QRect cr = contentsRect();
    m_num_widget->setGeometry(QRect(cr.left(), cr.top(), getLineNumberWidgetWidth(), cr.height()));
}

void TextEditor::keyPressEvent(QKeyEvent *event)
{
    if (m_tab_policy == TabPolicy_Spaces && event->key() == Qt::Key_Tab) {

        QString spaces;
        for (int i = 0; i < m_tab_size; ++i) {
            spaces.append(' ');
        }
        this->insertPlainText(spaces);
    }
    else {
        QPlainTextEdit::keyPressEvent(event);
    }
}

void TextEditor::slotOnBlockCountChanged(int)
{
    setViewportMargins(getLineNumberWidgetWidth(), 0, 0, 0);
}

void TextEditor::slotOnUpdateRequest(const QRect &rect, int dy)
{
    if (dy) {
        m_num_widget->scroll(0, dy);
    }
    else {
        m_num_widget->update(0, rect.y(), m_num_widget->width(), rect.height());
    }

    if (rect.contains(viewport()->rect())) {
        slotOnBlockCountChanged(0);
    }
}

void TextEditor::slotOnCursorPositionChanged()
{
    highlightCurrentLine();
}

void TextEditor::slotOnCustomContextMenuRequest(const QPoint &pos)
{
    QMenu menu;
    QAction *act_undo = menu.addAction(GET_ICON("undo"), tr("Undo"));
    QAction *act_redo = menu.addAction(GET_ICON("redo"), tr("Redo"));
    menu.addSeparator();
    QAction *act_copy = menu.addAction(GET_ICON("copy"), tr("Copy"));
    QAction *act_cut = menu.addAction(GET_ICON("cut"), tr("Cut"));
    QAction *act_paste = menu.addAction(GET_ICON("paste"), tr("Paste"));
    QAction *act_delete = menu.addAction(GET_ICON("backspace"), tr("Delete"));
    menu.addSeparator();
    QAction *act_select = menu.addAction(GET_ICON("select_all"), tr("Select All"));

    QString selected_text = getSelectedText();

    act_undo->setEnabled(this->document()->isUndoAvailable());
    act_redo->setEnabled(this->document()->isRedoAvailable());
    act_copy->setEnabled(!selected_text.isEmpty());
    act_cut->setEnabled(!selected_text.isEmpty());
    act_paste->setEnabled(!qApp->clipboard()->text().isEmpty());
    act_delete->setEnabled(!selected_text.isEmpty());
    act_select->setEnabled(!this->document()->isEmpty());

    QAction *act = menu.exec(QCursor::pos());

    if (!act) {
        return;
    }

    if (act == act_undo) {
        // undo
        this->undo();
    }
    else if (act == act_redo) {
        // redo
        this->redo();
    }
    else if (act == act_copy) {
        // copy
        this->copy();
    }
    else if (act == act_cut) {
        // cut
        this->cut();
    }
    else if (act == act_paste) {
        // paste
        this->paste();
    }
    else if (act == act_delete) {
        // delete
        this->insertPlainText("");
    }
    else if (act == act_select) {
        // select all
        this->selectAll();
    }
    else {

    }
}

void TextEditor::highlightCurrentLine()
{
    QList<QTextEdit::ExtraSelection> extra_selection = m_extra_selection;

    QTextEdit::ExtraSelection selection;

    QString scheme_name = domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_SchemeName).toString();
    QColor line_color = QColor(domy_config->getColorSchemeValue(scheme_name, SettingsKey_CurrentLine).toString());

    selection.format.setBackground(line_color);
    selection.format.setProperty(QTextFormat::FullWidthSelection, true);
    selection.cursor = textCursor();
    selection.cursor.clearSelection();
    extra_selection.push_front(selection);

    setExtraSelections(extra_selection);
}

int TextEditor::getLineNumberWidgetWidth()
{
    return (3 + fontMetrics().width(QLatin1Char('9')) * 5);
}

QTextDocument::FindFlags TextEditor::getFindFlags(bool is_backward, bool is_case_sensitive, bool is_whole_word)
{
    QTextDocument::FindFlags flags;
    if (is_backward) {
        flags |= QTextDocument::FindBackward;
    }

    if (is_case_sensitive) {
        flags |= QTextDocument::FindCaseSensitively;
    }

    if (is_whole_word) {
        flags |= QTextDocument::FindWholeWords;
    }

    return flags;
}

/**
 * @brief LineNumberWidget
 */
LineNumberWidget::LineNumberWidget(TextEditor *edit_widget)
    : QWidget(edit_widget),
      m_edit_widget(edit_widget),
      m_bg_color(Qt::lightGray),
      m_text_color(Qt::black)
{
    Q_ASSERT(m_edit_widget);
}

void LineNumberWidget::setBackgroundColor(const QColor &color)
{
    m_bg_color = color;

    update();
}

void LineNumberWidget::setTextColor(const QColor &color)
{
    m_text_color = color;

    update();
}

void LineNumberWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(event->rect(), m_bg_color);

    QTextBlock block = m_edit_widget->firstVisibleBlock();
    int block_number = block.blockNumber();
    int top = (int)m_edit_widget->blockBoundingGeometry(block).translated(m_edit_widget->contentOffset()).top();
    int bottom = top + (int)m_edit_widget->blockBoundingRect(block).height();

    while (block.isValid() && top <= event->rect().bottom()) {
        if (block.isVisible() && bottom >= event->rect().top()) {
            QString number = QString::number(block_number + 1);

            if (block_number == m_edit_widget->textCursor().blockNumber()) {
                // 突出显示当前行的行号
                QFont f = this->font();
                f.setWeight(QFont::Black);
                painter.setFont(f);
            }
            else {
                painter.setFont(this->font());
            }

            if (block_number == m_edit_widget->m_err_line) {
                // 高亮显示错误行行号
                painter.setPen(QColor(Qt::red).lighter(120));
            }
            else {
                painter.setPen(m_text_color);
            }
            painter.drawText(0, top, width() - 5, fontMetrics().height(),
                             Qt::AlignRight, number);
        }

        block = block.next();
        top = bottom;
        bottom = top + (int)m_edit_widget->blockBoundingRect(block).height();
        ++block_number;
    }
}

/**
 * @brief CodecNameComboBox
 */
CodecNameComboBox::CodecNameComboBox(QWidget *parent)
    : QComboBox(parent)
{
    this->addItems(Utils::getAllCodecNames());

    this->setStyleSheet("QComboBox { background-color: white; border-style: none; }"
                        "QComboBox::drop-down { border-style: none; }");

    this->setCurrentText(domy_config->getSettingsValue(SettingsGroup_Editor, SettingsKey_Encoding).toString());
}

CodecNameComboBox::~CodecNameComboBox()
{

}

void CodecNameComboBox::wheelEvent(QWheelEvent *event)
{
    event->ignore();
}

/**
 * @brief The TextFuncActKey enum
 * @note  文本功能key
 */
enum TextFuncKey {
    TextFunc_Find = 0,
    TextFunc_Case,
    TextFunc_Whole,
    TextFunc_Prev,
    TextFunc_Next,
    TextFunc_Replace,
    TextFunc_ShortcutNode,
    TextFunc_ShortcutComment,
    TextFunc_ShortcutApostrophe,
    TextFunc_ShortcutQuotation,
    TextFunc_ShortcutParen,
    TextFunc_ShortcutBracket,
    TextFunc_ShortcutBrace
};

/**
 * @brief EditorBottomBar
 */
EditorBottomBar::EditorBottomBar(QWidget *parent)
    : QToolBar(parent),
      m_label_length(new QLabel(this)),
      m_label_lines(new QLabel(this)),
      m_label_row(new QLabel(this)),
      m_label_column(new QLabel(this)),
      m_le_find(new QLineEdit(this)),
      m_le_replace(new QLineEdit(this)),
      m_combo_codec(new CodecNameComboBox(this)),
      m_is_case_sensitive(false),
      m_is_whole_word(false)
{
    this->setIconSize(QSize(16, 16));

    // find
    QLabel *label_find = new QLabel(tr(" Find: "), this);
    this->addWidget(label_find);

    m_le_find->setPlaceholderText(tr("Search for ..."));
    m_le_find->setFixedWidth(200);
    this->addWidget(m_le_find);

    QAction *act = this->addAction(GET_ICON("find"), tr("Find"));
    act->setData(static_cast<int>(TextFunc_Find));

    this->addSeparator();

    act = this->addAction(GET_ICON("prev"), tr("Previous"));
    act->setData(static_cast<int>(TextFunc_Prev));

    act = this->addAction(GET_ICON("next"), tr("Next"));
    act->setData(static_cast<int>(TextFunc_Next));

    this->addSeparator();

    act = this->addAction(GET_ICON("case"), tr("Case Sensitive"));
    act->setCheckable(true);
    act->setData(static_cast<int>(TextFunc_Case));

    act = this->addAction(GET_ICON("whole"), tr("Whole Words Only"));
    act->setCheckable(true);
    act->setData(static_cast<int>(TextFunc_Whole));

    this->addSeparator();


    // replace
    QLabel *label_replace = new QLabel(tr(" Replace With: "), this);
    this->addWidget(label_replace);

    m_le_replace->setPlaceholderText(tr("Replace With ..."));
    m_le_replace->setFixedWidth(200);
    this->addWidget(m_le_replace);

    act = this->addAction(GET_ICON("replace"), tr("Replace All"));
    act->setData(static_cast<int>(TextFunc_Replace));

    this->addSeparator();

    // spacer
    QFrame *spacer = new QFrame(this);
    spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    this->addWidget(spacer);

    this->addSeparator();

    // len and rows
    this->addWidget(m_label_length);
    this->addWidget(m_label_lines);

    this->addSeparator();

    this->addWidget(m_label_row);
    this->addWidget(m_label_column);

    this->addSeparator();

    // codec
    this->addWidget(m_combo_codec);

    m_label_length->setFixedWidth(100);
    m_label_lines->setFixedWidth(100);
    m_label_row->setFixedWidth(100);
    m_label_column->setFixedWidth(100);

    m_le_find->setClearButtonEnabled(true);
    m_le_replace->setClearButtonEnabled(true);

    setInfo(0, 0, 0, 0);

    connect(m_combo_codec, SIGNAL(currentTextChanged(const QString &)),
            this, SIGNAL(signalCodecNameChanged(const QString &)));
    connect(this, SIGNAL(actionTriggered(QAction *)),
            this, SLOT(slotOnActionTriggered(QAction *)));
    connect(m_le_find, SIGNAL(textChanged(const QString &)),
            this, SLOT(slotOnFindTextChanged(const QString &)));
    connect(m_le_find, SIGNAL(returnPressed()),
            this, SLOT(slotOnFindTextEditFinished()));
    connect(m_le_replace, SIGNAL(returnPressed()),
            this, SLOT(slotOnReplaceTextEditFinished()));
}

EditorBottomBar::~EditorBottomBar()
{

}

void EditorBottomBar::setCodecName(const QString &name)
{
    m_combo_codec->setCurrentText(name);
}

void EditorBottomBar::setInfo(int length, int lines, int row, int column)
{
    m_label_length->setText(tr("Length: %1").arg(length));
    m_label_lines->setText(tr("Lines: %1").arg(lines));
    m_label_row->setText(tr("Row: %1").arg(row));
    m_label_column->setText(tr("Col: %1").arg(column));
}

void EditorBottomBar::setCodecNameEditable(bool is_editable)
{
    m_combo_codec->setEnabled(is_editable);
}

bool EditorBottomBar::isCaseSensitive() const
{
    return m_is_case_sensitive;
}

bool EditorBottomBar::isWholeWord() const
{
    return m_is_whole_word;
}

void EditorBottomBar::setFindText(const QString &text)
{
    m_le_find->setText(text);
    m_find_text = text;
}

QString EditorBottomBar::getFindText() const
{
    return m_le_find->text();
}

QString EditorBottomBar::getReplaceText() const
{
    return m_le_replace->text();
}

void EditorBottomBar::slotOnActionTriggered(QAction *action)
{
    TextFuncKey key = static_cast<TextFuncKey>(action->data().toInt());
    switch (key) {
    case TextFunc_Find:
    {
        QString find_text = m_le_find->text();

        if (!find_text.isEmpty()) {
            m_find_text = find_text;
            emit signalFind(find_text, m_is_case_sensitive, m_is_whole_word);
        }
    }
        break;
    case TextFunc_Case:
        m_is_case_sensitive = action->isChecked();
        break;
    case TextFunc_Whole:
        m_is_whole_word = action->isChecked();
        break;
    case TextFunc_Next:
    {
        QString find_text = m_le_find->text();
        if (!find_text.isEmpty()) {
            emit signalFindNext(find_text, m_is_case_sensitive, m_is_whole_word);
        }
    }
        break;
    case TextFunc_Prev:
    {
        QString find_text = m_le_find->text();
        if (!find_text.isEmpty()) {
            emit signalFindPrevious(find_text, m_is_case_sensitive, m_is_whole_word);
        }
    }
        break;
    case TextFunc_Replace:
    {
        QString before = m_le_find->text();
        QString after = m_le_replace->text();
        if (!before.isEmpty() && !after.isEmpty()) {
            emit signalReplace(before, after, m_is_case_sensitive, m_is_whole_word);
        }
    }
        break;
    default:
        break;
    }
}

void EditorBottomBar::slotOnFindTextChanged(const QString &text)
{
    if (m_find_text != text && !m_find_text.isEmpty()) {
        m_find_text.clear();
        emit signalClearFindResult();
    }
}

void EditorBottomBar::slotOnFindTextEditFinished()
{
    QString find_text = m_le_find->text();
    if (!find_text.isEmpty()) {
        m_find_text = find_text;
        emit signalFind(find_text, m_is_case_sensitive, m_is_whole_word);
    }
}

void EditorBottomBar::slotOnReplaceTextEditFinished()
{
    QString before = m_le_find->text();
    QString after = m_le_replace->text();
    if (!before.isEmpty() && !after.isEmpty()) {
        emit signalReplace(before, after, m_is_case_sensitive, m_is_whole_word);
    }
}

} // namespace Domy
